#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "flv.h"
#include "buffer.h"

#ifdef DEBUG
#include <errno.h>
int errno;
#endif
int __write_generic(Stream *stream, CBuffer *buf, int *advance, void *data)
{
	FlvTag* tag;
	if(stream == NULL || buf == NULL || buf->m_buffer.data == NULL) return -E_ERR;
	tag = (FlvTag*)data;
	if(tag->type != stream->type) return -E_ERR;
	if(tag->type == FLV_TAG_TYPE_AUDIO)
		stream->hdr.audio = tag->hdr.audio;
	else if(tag->type == FLV_TAG_TYPE_VIDEO)
		stream->hdr.video = tag->hdr.video;
	return buf->Write(tag->data,tag->dataSize-1);
}
void __free_generic(Stream *stream)
{
	if(stream) free(stream);
}
int writeHeader(CBuffer *buf, uc version, uc flags) 
{
	int ret = 0;	
	ret += buf->Writec('F');
	ret += buf->Writec('L');
	ret += buf->Writec('V');
	ret += buf->Writec(version);
	ret += buf->Writec(flags);
	ret += buf->WriteUI32(9);
	ret += buf->WriteUI32(0);
	return ret;
}
ui readHeader(CBuffer *buf, uc* version, uc* flags) 
{
	ui tmp;
	unsigned offset;
	int ret;
	ret = buf->ReadUI24(&tmp);
	if(ret < 0) return ret;
	if ((tmp>>16) != 'F' && (tmp>>8) != 'L' && (tmp) != 'V') {
		printf("Not a flv-file\n");
		return -E_ERR;
	}
	ret = buf->Readc(&tmp);
	if(ret < 0) return ret;
	*version = tmp;
	ret = buf->Readc(&tmp);
	if(ret < 0) return ret;
	*flags = tmp;
	ret = buf->ReadUI32(&tmp);
	if(ret < 0) return ret;
	offset = tmp;
	ret = buf->ReadUI32(&tmp);
	if(ret < 0) return ret;
	return offset;
} 
int readTagHdr(CBuffer *buf,FlvTag *tag)
{
	int ret;
	ui tmp;
	ret = buf->Readc(&tag->type);
	if(ret < 0) return ret;
	ret = buf->ReadUI24(&tag->dataSize);
	if(ret < 0) return ret;
	ret = buf->ReadUI24(&tag->timestamp);
	if(ret < 0) return ret;
	ret = buf->ReadUI32(&tmp);
	if(ret < 0) return ret;
	if(tmp != 0) return -E_ERR;
	return 0;
}
 void writeTagHdr(CBuffer* buf, int type, int tp,  int size)
{		
	buf->Writec(type);					
	buf->WriteUI24(size );			
	buf->WriteUI24(tp);				
	buf->WriteUI32(0);					
}
void writeVideoHdr(CBuffer* buf,VideoHDR *hdr)
{
	uc h;
	h = hdr->codec | hdr->frameType;
	buf->Writec(h);
}
int readVideoHdr(CBuffer* buf,FlvTag *tag)
{
	ui h;
	int ret;
	ret = buf->Readc(&h);
	if(ret < 0) return ret;
	tag->hdr.video.codec = 0xf0 & h;
	tag->hdr.video.frameType = 0x0f & h;
	return 0;
}
void writeAudioHdr(CBuffer* buf,AudioHDR *hdr)
{
	ui h;
	h = hdr->format | hdr->samplingRate | hdr->sampleSize | hdr->channel;
	buf->Writec(h);
}
int readAudioHdr(CBuffer* buf,FlvTag *tag)
{
	ui h;
	int ret;
	ret = buf->Readc(&h);
	if(ret < 0) return ret;
	tag->hdr.audio.format = 0xf0 & h;
	tag->hdr.audio.samplingRate = 0xc & h;
	tag->hdr.audio.sampleSize = 0x2 & h;
	tag->hdr.audio.channel = 0x1 & h;
	return 0;
}
int setTimestamp(CStream *stream, int tp)
{
	if(stream->m_stream.offset < 0)
		stream->m_stream.offset = tp;
	else
		stream->m_stream.timestamp = tp - stream->m_stream.offset;
	if(stream->m_stream.timestamp < 0) return -E_ERR;
	return 0;
}
#define AV_HEADER_SIZE 	1
int writeTag(CStream *stream, CBuffer *buf, int tp, int len, void *data) 
{
	int dataSize, tagSize, padd;
	if(stream->m_stream.writeData == NULL) return -E_ERR;
	if(setTimestamp(stream, tp) < 0) return -E_ERR;
	// skip header
	if(buf->Seek(FLV_TAG_HEADER_SIZE + AV_HEADER_SIZE, BUF_CUR) < 0) return -E_ERR;
	dataSize = stream->m_stream.writeData(&stream->m_stream, buf, &len, data);
	if(dataSize <= 0)
	{
		int l = FLV_TAG_HEADER_SIZE + AV_HEADER_SIZE;
		buf->Seek(-l, BUF_CUR);
		return -E_ERR;
	}
	tagSize = FLV_TAG_HEADER_SIZE + AV_HEADER_SIZE + dataSize;
	padd = buf->WriteUI32(tagSize);
	// rewind 
	buf->Seek(-(tagSize + padd), BUF_CUR);
	writeTagHdr(buf, stream->m_stream.type, stream->m_stream.timestamp, dataSize + AV_HEADER_SIZE);
	if(stream->m_stream.type == FLV_TAG_TYPE_AUDIO)
	{
		writeAudioHdr(buf, &stream->m_stream.hdr.audio);
	}
	else if(stream->m_stream.type == FLV_TAG_TYPE_VIDEO)
	{
		writeVideoHdr(buf, &stream->m_stream.hdr.video);
	}
	else 
	{
		printf("stream->type not set %i\n", stream->m_stream.type);
		return -E_ERR;				// fixme 
	}
	// next tag
	if(buf->Seek(dataSize + padd, BUF_CUR) < 0) return -E_ERR;
	buf->Flush();
	return len;
}

int CFlvStream::WriteAudioTag(int tp, int len, void *data)
{
	if(tp < 0 || len < 0) return -E_STREAM;
	if(m_flvstream.audio == NULL)
	{
		printf("No audio stream registered\n");
		return -E_STREAM;
	}
	return writeTag(m_flvstream.audio, m_flvstream.buf, tp, len, data);
}
int CFlvStream::WriteVideoTag(int tp, int len, void *data)
{
	if(tp < 0 || len < 0) return -E_STREAM;
	if(m_flvstream.video == NULL)
	{
		printf("No video stream registered\n");
		return -E_STREAM;
	}
	return writeTag(m_flvstream.video, m_flvstream.buf, tp, len, data);
}
void CFlvStream::Free()
{
	m_flvstream.buf->Free();
	free(this);
}
int CFlvStream::ReadNextTag(FlvTag *tag)
{
	int ret; 
	if(tag == NULL) return -E_ERR;
	if(readTagHdr(m_flvstream.buf, tag) < 0) return -E_ERR;
	if(tag->type == FLV_TAG_TYPE_AUDIO)
	{
		if(readAudioHdr(m_flvstream.buf, tag) < 0)
			return -E_ERR;
	}
	else if (tag->type == FLV_TAG_TYPE_VIDEO)
	{
		if(readVideoHdr(m_flvstream.buf, tag) < 0) return -E_ERR;
	}
	else 
	{
		printf("tagtype %i\n", tag->type);
	}
	tag->data = m_flvstream.buf->GetData(tag->dataSize - 1);
	if(tag->data == NULL) return -E_ERR;
	ret = m_flvstream.buf->Seek(tag->dataSize +3, BUF_CUR);
	if(ret < 0) return -E_ERR;
	return 0;
}
CFlvStream* CFlvStream::Open(const char *name)
{
	CFlvStream *flv;
	if(name == NULL) return NULL;
	flv = new CFlvStream;
	if(flv == NULL) return NULL;
	flv->m_flvstream.buf = CBuffer::Create(name, BUF_FILE);
	if(flv->m_flvstream.buf == NULL) 
	{
		free(flv);
		return NULL;
	}
	flv->m_flvstream.audio = NULL;
	flv->m_flvstream.video = NULL;
	if(readHeader(flv->m_flvstream.buf, &flv->m_flvstream.version, &flv->m_flvstream.flags) < 0)
	{
		printf("No FLV file \n");
		flv->m_flvstream.buf->Free();
		free(flv);
		return NULL;
	}
	return flv;
}
CFlvStream* CFlvStream::Create(const char *name, CStream *audio, CStream *video, int version) 
{
	if(version != FLV_VERSION_1)
	{
		printf("Can't create flv streams with version %i\n", version);
		return NULL;
	}
	CFlvStream* flv =new CFlvStream;
	if(flv == NULL) return NULL;
	flv->m_flvstream.version = version;
	flv->m_flvstream.buf = CBuffer::Create(name, BUF_STREAM);
	if(flv->m_flvstream.buf == NULL) 
	{
		free(flv);
		return NULL;
	}
	if(audio != NULL)
	{
		flv->m_flvstream.audio = audio;
		flv->m_flvstream.flags |= FLV_FLAG_AUDIO;
	}
	if(video != NULL)
	{
		flv->m_flvstream.video = video;
		flv->m_flvstream.flags |= FLV_FLAG_VIDEO;
	}
	if(flv->m_flvstream.flags == 0)
	{
		printf("Can't create flv stream: no streams defined\n");
		return NULL;
	}
	writeHeader(flv->m_flvstream.buf, flv->m_flvstream.version, flv->m_flvstream.flags);
	printf("write %i\n", flv->m_flvstream.buf->Flush());
	return flv;
}
/*int CFlvStream::FlvStream_write(FlvStream *flv, const char *name)
{
	return CBuffer::WriteToFile(flv->buf, name);
}*/
void CStream::Free()
{
	
}
void CStream::Rewind()
{
	m_stream.timestamp = 0;
	m_stream.offset = -1;
	if(m_stream.resetStream != NULL)
		m_stream.resetStream(&this->m_stream);
}
CStream* CStream::Create(int type)
{
	CStream *stream;
	if(type != STREAM_AUDIO && type != STREAM_VIDEO) return NULL;
	stream = new CStream;
	if(stream == NULL) return NULL;
	stream->m_stream.type = type;
	stream->m_stream.writeData = __write_generic;
	stream->m_stream.readData = NULL;
	stream->m_stream.resetStream = NULL;
	stream->m_stream.freeStream = __free_generic;
	stream->Rewind();
	return stream;
}